using System;
using System.Drawing;
using System.Collections.Generic;
using Clandestine.Engines.Field;

namespace Shlick
{
    public class PaintTilesCommand : ICommand
    {
    	private class PaintTileCommand : ICommand
    	{	
    		private Tile? Tile;
    		private Point Position;
    		private Layer Layer; 
    		public string Name { get { return "Paint Tile"; } } 
    		
    		public PaintTileCommand(Layer layer, Tile? tile, Point pos)
    		{	
    			this.Layer = layer;
    			this.Tile = tile;
    			this.Position = pos;
    		}
    		
    		public void Do()
    		{
				// Okay, paint the tile. PaintTile op returns the tile that was there before we changed it (null for no tile). Remember it!
				Tile = Layer.AddTile(Tile, Position.X, Position.Y);
    		}
    		
    		public void Undo()
    		{
    			// Same as before! -- Also returns the old tile.
    			// We assume (quite safely) nothing else is going to change the tile.
    			Tile = Layer.AddTile(Tile, Position.X, Position.Y);
    		}
    	}
    	
		private Stack<ICommand> doStack = new Stack<ICommand>();
		private Stack<ICommand> undoStack = new Stack<ICommand>();
    	private string name = string.Empty;
    	public string Name { get { return name; } }
    	  	
    	private PaintTilesCommand(string name)
   		{
   			this.name = name;
   		}
   		
   		// Layers have .Positions... the TilePainterTool doesn't accomodate this.
   		private static void correctLayerCoordinates(Layer layer, ref Rectangle paintArea)
   		{
   			paintArea.X -= layer.Position.X;
   			paintArea.Y -= layer.Position.Y;
   		}
   		
   		public static PaintTilesCommand PaintTiles(Layer layer, Spritesheet spritesheet, Rectangle spritesheetSelection, Rectangle paintArea, bool Temporary)
   		{
   			PaintTilesCommand cmd = new PaintTilesCommand("Paint Tiles");
			int numTilesX = spritesheet.Width / spritesheet.GridSize;
   			
   			// Layers have .Position's, accomodate for this!
   			correctLayerCoordinates(layer, ref paintArea);
   			
            for (int y = 0; y < paintArea.Height; y++)
                for (int x = 0; x < paintArea.Width; x++)
                {
                	// selection x and y are the position in the tile selection we're painting
                	// we repeat once we reach the end!
                	int selectionX = spritesheetSelection.X + (x % spritesheetSelection.Width);
                	int selectionY = spritesheetSelection.Y + (y % spritesheetSelection.Height);
                	
                	// And the position of this on the spritesheet
                	int tilenumber = (selectionY * numTilesX) + selectionX;
                	
                	// And paint -- depending on whether it's temporary or not!
                	if (Temporary)
                		// if it's a tempoary paint, just paint as we're not returning an ICommand
                		layer.AddTemporaryTile(new Tile(spritesheet, tilenumber), paintArea.X + x, paintArea.Y + y);
                	else
                		// Now, this is a real paint op, so we're going to defer it till later.
                		// Doesn't matter here whether it's a queue or a stack as order is unimporant.
                		cmd.doStack.Push(new PaintTileCommand(layer, new Tile(spritesheet, tilenumber), new Point(paintArea.X + x, paintArea.Y + y)));
				}
			
			if (!Temporary)
				return cmd;
			else
				return null;
   		}
		
		public static PaintTilesCommand PaintTiles(Layer layer, Tile?[,] tiles, Point topLeftPaintArea, bool Temporary)
		{
			PaintTilesCommand cmd = new PaintTilesCommand("Move/Paste Tiles");
			
			Rectangle paintArea = new Rectangle(topLeftPaintArea, new Size(tiles.GetUpperBound(0), tiles.GetUpperBound(1)));
			correctLayerCoordinates(layer, ref paintArea);
			
			for (int y = 0; y < paintArea.Height; y++)
				for (int x = 0; x < paintArea.Width; x++)
				{
					if (Temporary)
					{
						if (tiles[x,y].HasValue)
							layer.AddTemporaryTile(tiles[x,y].Value, paintArea.X + x, paintArea.Y + y);
						else
							layer.ClearTile(x, y);
					}
					else
						cmd.doStack.Push(new PaintTileCommand(layer, tiles[x,y], new Point(paintArea.X + x, paintArea.Y + y)));
				}
			
			if (!Temporary)
				return cmd;
			else
				return null;
		}
   		
   		public static PaintTilesCommand EraseTiles(Layer layer, Rectangle clearArea)
   		{
   			PaintTilesCommand cmd = new PaintTilesCommand("Erase Tiles");
   			
   			// Layers have .Position's, accomodate for this!
   			correctLayerCoordinates(layer, ref clearArea);
   			
			// Loop through and add the individual clear tiles operations to the stack
			// Remember painting null = erase! (Although .ClearTile() works too.
			for (int x = clearArea.X; x < (clearArea.X + clearArea.Width); x++)
				for (int y = clearArea.Y; y < (clearArea.Y + clearArea.Height); y++)
					cmd.doStack.Push(new PaintTileCommand(layer, null, new Point(x, y)));
					
			return cmd;
   		}
   		
   		public void Do()
   		{
   			// Do everything on our do stack and add it to our undo stack!
   			while (this.doStack.Count > 0)
   			{
   				ICommand cmd = this.doStack.Pop();
   				cmd.Do();
   				this.undoStack.Push(cmd);
   			}
   		}
   		
   		public void Undo()
   		{
   			// Do the opposite!
   			while (this.undoStack.Count > 0)
   			{
   				ICommand cmd = this.undoStack.Pop();
   				cmd.Undo();
   				this.doStack.Push(cmd);
   			}
   		}
    }
}

